﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

namespace ReactiveGraph.Core.Extensions
{
    public static class ObservableCollectionExtensions
    {
        public static IObservable<U> WhileInCollection<T, U>(this ObservableCollection<T> collection,
                                                             Func<T, IObservable<U>> behaviour)
        {
            return WhileInCollection(
                collection.AsEnumerable(),
                collection.ItemAdded(),
                collection.ItemRemoved(),
                behaviour);
        }

        public static IObservable<U> WhileInCollection<T, U>(this RxCollection<T> collection,
                                                             Func<T, IObservable<U>> behaviour)
        {
            return WhileInCollection(
                collection.AsEnumerable(),
                collection.Changed.SelectMany(args => args.AddedItems),
                collection.Changed.SelectMany(args => args.RemovedItems),
                behaviour);
        }

        private static IObservable<U> WhileInCollection<T, U>(this IEnumerable<T> collection, IObservable<T> added,
                                                             IObservable<T> removed, Func<T, IObservable<U>> behaviour)
        {
            return added.Merge(collection.ToObservable())
                .SelectMany(newItem => behaviour(newItem).TakeUntil(removed.Where(arg => arg.Equals(newItem))));
        }

        private static IObservable<NotifyCollectionChangedEventArgs> ToNotificationObservable<T>(
            this ObservableCollection<T> collection)
        {
            return Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
                eventHandler => ((sender, args) => eventHandler(sender, args)),
                handler => collection.CollectionChanged += handler,
                handler => collection.CollectionChanged -= handler)
                .Select(e => e.EventArgs);
        }

        private static IObservable<IEnumerable<T>> ItemsAdded<T>(this ObservableCollection<T> obs)
        {
            return obs.ToNotificationObservable()
                .Where(args => args.Action == NotifyCollectionChangedAction.Add)
                .Select(eventArgs => eventArgs.NewItems.Cast<T>());
        }

        private static IObservable<T> ItemAdded<T>(this ObservableCollection<T> obs)
        {
            return obs.ItemsAdded().Select(arg => arg.ToObservable()).Merge();
        }

        private static IObservable<T> ItemRemoved<T>(this ObservableCollection<T> obs)
        {
            return obs.ItemsRemoved().Select(arg => arg.ToObservable()).Merge();
        }

        private static IObservable<IEnumerable<T>> ItemsRemoved<T>(this ObservableCollection<T> obs)
        {
            return obs.ToNotificationObservable()
                .Where(args => args.Action == NotifyCollectionChangedAction.Remove)
                .Select(eventArgs => eventArgs.OldItems.Cast<T>());
        }
    }
}